/**
 * WYSIWYG editor class.
 *
 * @author Andrey Anisimov <hercules@inbox.ru>
 */

import core.ui.Window;
import core.ui.Event;
import core.util.Listeners;

class HtmlEditor extends Window {
	
	this.HtmlEditor = function(parent, x, y, width, height) {
		super(parent, x, y, width, height);
		this.listeners = new Listeners([
            'onEditorLoaded',
            'onEditorSelectionChange'
        ]);
	}

	/**
	 * Adds the new editor listener.
	 *
	 * @param listener (object) The listener to add.
	 */
	this.addEditorListener = function(listener) {
		this.listeners.add(listener);
	}

	/**
	 * Remove new editor listener.
	 *
	 * @param listener (object) The listener to remove.
	 */
	this.removeEditorListener = function(listener) {
		this.listeners.remove(listener);
	}

	/**
	 * Calls when editor is loaded and ready to perform editing.
	 */
	this.fireLoaded = function() {
		this.listeners.call('onEditorLoaded', [ this ]);
	}

	/**
	 * Fires when the user selects or unselects some text part.
	 */
	this.fireSelectionChange = function() {
		this.selection = this.getSelection();
		this.listeners.call('onEditorSelectionChange', [ this ]);
	}

	/**
	 * Sets text to edit.
	 *
	 * @param text (string) HTML text
	 */	
	this.setText = function(html) {
		var body = this.document.getElementsByTagName('body')[0];
		if (body) {
			body.innerHTML = html;
			try {
				// FIX: This needed to fix a bug in Firefox when user cannot delete
				// current text without typing something else.
				this.execCommand('InsertHtml', '<!--Powered by HtmlEditor v.0.1.1-->');
			} catch (e) {
				// IE doesn't support InsertHtml command.
			}
		}
	}
	
	/**
	 * Returns current text in editor.
	 *
	 * @return string
	 */
	this.getText = function() {
		var body = this.document.getElementsByTagName('body')[0];
		return (body) ? body.innerHTML : '';
	}

	/**
	 * Returns the currently selected text part.
	 *
	 * @return string
	 */
	this.getSelectedText = function() {
		return ('function' == typeof this.selection.toString)
			? this.selection.toString()
			: this.selection.text;
	}
	
	/**
	 * Makes an indent.
	 */
	this.indent = function() {
		this.execCommand('Indent');
	}
	
	/**
	 * Checks whether the cursor is currently on an indent position.
	 *
	 * @return bool
	 */
	this.isIndent = function() {
		return this.queryCommandState('Indent');
	}
	
	/**
	 * Makes an outdent.
	 */
	this.outdent = function() {
		this.execCommand('Outdent');
	}
	
	/**
	 * Checks whether the cursor is currently on an outdent position.
	 *
	 * @return bool
	 */
	this.isOutdent = function() {
		return this.queryCommandState('Outdent');
	}
	
	/**
	 * Sets the selected text font face.
	 *
	 * @param face (string) font face
	 */
	this.setFontFace = function(n) {
		this.execCommand('FontName', n);
	}
	
	/**
	 * Returns currently selected text's font face.
	 *
	 * @return string
	 */
	this.getFontFace = function() {
		return this.queryCommandValue('FontName');
	}
	
	/**
	 * Sets the selected text font size.
	 *
	 * @param size (string) font size
	 */
	this.setFontSize = function(s) {
		this.execCommand('FontSize', s);
	}
	
	/**
	 * Returns currently selected text's font size.
	 *
	 * @return string
	 */
	this.getFontSize = function() {
		return this.queryCommandValue('FontSize');
	}
	
	/**
	 * Sets the selected text fore color.
	 *
	 * @param color (string) color value
	 */
	this.setForeColor = function(c) {
		this.execCommand('ForeColor', c);
	}
	
	/**
	 * Returns the current fore color.
	 *
	 * @return string
	 */
	this.getForeColor = function(c) {
		return this.queryCommandValue('ForeColor');
	}
	
	/**
	 * Sets the background color.
	 *
	 * @param color (string) color value
	 */
	this.setBackColor = function(c) {
		this.execCommand('BackColor', c);
	}
	
	/**
	 * Returns the current background color.
	 *
	 * @return string
	 */
	this.getBackColor = function(c) {
		return this.queryCommandValue('BackColor');
	}
	
	/**
	 * Sets selected text bold.
	 */
	this.setBold = function() {
		this.execCommand('Bold');
	}
	
	/**
	 * Gets whether selected text is bold.
	 *
	 * @return bool
	 */
	this.isBold = function() {
		return this.queryCommandState('Bold');
	}
	
	/**
	 * Sets selected text italic.
	 */
	this.setItalic = function() {
		this.execCommand('Italic');
	}
	
	/**
	 * Gets whether selected text is italic.
	 *
	 * @return bool
	 */
	this.isItalic = function() {
		return this.queryCommandState('Italic');
	}
	
	/**
	 * Sets selected text underline.
	 */
	this.setUnderline = function() {
		this.execCommand('Underline');
	}
	
	/**
	 * Gets whether selected text is underlined.
	 *
	 * @return bool
	 */
	this.isUnderline = function() {
		return this.queryCommandState('Underline');
	}
	
	/**
	 * Makes left justification.
	 */
	this.justifyLeft = function() {
		this.execCommand('JustifyLeft');
	}
	
	/**
	 * Checks whether selected text is justified left.
	 *
	 * @return bool
	 */
	this.isJustifiedLeft = function() {
		return this.queryCommandState('JustifyLeft');
	}
	
	/**
	 * Makes right justification.
	 */
	this.justifyRight = function() {
		this.execCommand('JustifyRight');
	}
	
	/**
	 * Checks whether selected text is justified right.
	 *
	 * @return bool
	 */
	this.isJustifiedRight = function() {
		return this.queryCommandState('JustifyRight');
	}
	
	/**
	 * Makes center justification.
	 */
	this.justifyCenter = function() {
		this.execCommand('JustifyCenter');
	}
	
	/**
	 * Checks whether selected text is justified center.
	 *
	 * @return bool
	 */
	this.isJustifiedCenter = function() {
		return this.queryCommandState('JustifyCenter');
	}
	
	/**
	 * Makes full text justification.
	 */
	this.justifyFull = function() {
		this.execCommand('JustifyFull');
	}
	
	/**
	 * Checks whether selected text is fully justified.
	 *
	 * @return bool
	 */
	this.isJustifiedFull = function() {
		return this.queryCommandState('JustifyFull');
	}
	
	/**
	 * Inserts text at the current position.
	 * Will throw an exception in IE since IE doesn't have such command.
	 *
	 * @param html (string) HTML to insert
	 */
	this.insertHtml = function(html) {
		this.execCommand('InsertHtml', html);
	}
	
	/**
	 * Wraps selected text by link.
	 *
	 * @param url (string) link url
	 */
	this.createLink = function(url) {
		this.execCommand('CreateLink', url)
	}
	
	/**
	 * Returns selected text link url.
	 *
	 * @return string
	 */
	this.getLink = function() {
		return this.queryCommandValue('CreateLink');
	}
	
	/**
	 * Removes link for selected text.
	 */
	this.unlink = function() {
		this.execCommand('Unlink');
	}
	
	/**
	 * Undoes edition.
	 */
	this.undo = function() {
		this.execCommand('Undo');
	}
	
	/**
	 * Checks whether user can undo previous change.
	 *
	 * @return bool
	 */
	this.canUndo = function() {
		return this.queryCommandState('Undo');
	}
	
	/**
	 * Redoes previous undo.
	 */
	this.redo = function() {
		this.execCommand('Redo');
	}

	/**
	 * Checks whether user can redo.
	 *
	 * @return bool
	 */	
	this.canRedo = function() {
		return this.queryCommandState('Redo');
	}
	
	/**
	 * Inserts ordered list at the current position.
	 */
	this.insertOrderedList = function() {
		this.execCommand('InsertOrderedList');
	}
	
	/**
	 * Checks whether selected text is an ordered list.
	 *
	 * @return bool
	 */
	this.isOrderedList = function() {
		return this.queryCommandState('InsertOrderedList');
	}
	
	/**
	 * Inserts unordered list at the current position.
	 */
	this.insertUnorderedList = function() {
		this.execCommand('InsertUnorderedList');
	}
	
	/**
	 * Checks whether selected text is a unordered list.
	 *
	 * @return bool
	 */
	this.isUnorderedList = function() {
		return this.queryCommandState('InsertUnorderedList');
	}

	/**
	 * @see Window.close
	 */
	this.close = function() {
		this.getElement().removeChild(this.iframe);
		delete this.document;
		delete this.iframe;
		super.close();
	}

	/**
	 * @protected
	 * Returns the browser-depent selection object.
	 *
	 * @return object
	 */
	this.getSelection = function() {
		var selection = '';
		if (this.document.selection){
			selection = this.document.selection.createRange();
		} else {
			selection = this.iframe.contentWindow.getSelection();
			selection = selection.getRangeAt(selection.rangeCount - 1);
		}
		return selection;
	}
	
	/**
	 * @protected
	 * Inits document design mode.
	 */
	this.initDesignMode = function() {
		try {
			this.document = this.iframe.contentWindow.document;
			this.document.designMode = 'on';
			// Switch to "off" css styling since IE doesn't supports it :(
			if ('function' == typeof this.document.queryCommandSupported) {
				try {
					if (this.document.queryCommandSupported('useCSS')) {
						this.document.execCommand('useCSS', false, false);
					} else if (this.document.queryCommandSupported('styleWithCSS')) {
						this.document.execCommand('styleWithCSS', false, false);
					}
				} catch (e) {
					// Currently mozilla doesn't support "queryCommandSupported"
					this.document.execCommand('styleWithCSS', false, false);
				}
			}
			// Attach events
			var types = [ 'mousedown', 'mouseup', 'focus' ];
			for (var i = 0; i < types.length; i++) {
				Event.addListener(this.document, types[i], bind(this, this.onMouseEvent));
			}
			types = [ 'keydown', 'keyup' ];
			for (var j = 0; j < types.length; j++) {
				Event.addListener(this.document, types[j], bind(this, this.onKeyboardEvent));
			}
		} catch (e) {
			// Do nothing
		}
	}
	
	/**
	 * @protected
	 * Executes the command for selected text.
	 * 
	 * @param name (string) The command name.
	 * @param value (string) The argument value.
	 * @throws Error Command execution fails.
	 */
	this.execCommand = function(name, value) {
		var result = false;
		try {
			if (!value) {
				value = null;
			}
			if ('function' == typeof this.selection.select) {
				// Restore selection in IE before execute
				this.selection.select();
			}
			result = this.document.execCommand(name, false, value);
		} catch (e) {
			throw new Error('Failed to execute command ' + name + ': ' + e);
		}
		this.iframe.contentWindow.focus();
		if ('function' == typeof this.iframe.focus) {
			this.iframe.focus();
		}
		return result;
	}
	
	/**
	 * @protected
	 * Gets the specified command state for the current selection.
	 * 
	 * @param name (string) The command name.
	 * @return bool
	 */
	this.queryCommandState = function(commandName) {
		return ('function' == typeof this.document.queryCommandState)
			? this.document.queryCommandState(commandName)
			: false;
	}
	
	/**
	 * @protected
	 * Gets the specified command value for the current selection.
	 * 
	 * @param name (string) The command name.
	 * @return string
	 */
	this.queryCommandValue = function(commandName) {
		return ('function' == typeof this.document.queryCommandValue)
			? this.document.queryCommandValue(commandName)
			: null;
	}
	
	/**
	 * @protected
	 * Creates the editor's root element (DIV).
	 *
	 * @return HTMLElement
	 */
	this.createElement = function() {
		var root = super.createElement();
		this.iframe = document.createElement('iframe');
		this.iframe.style.width = this.iframe.style.height = '100%';
		this.iframe.marginHeight = this.iframe.marginWidth = 0;
		this.iframe.style.border = this.iframe.frameBorder = 0;
		this.iframe.allowTransparency = true;
		this.iframe.src = 'about:blank';
		this.iframe.scrolling = 'no';
		this.iframe.id = this.getId() + '_iframe';
		this.loadHandler = bind(this, this.onLoad);
		Event.addListener(this.iframe, 'load', this.loadHandler);
		root.appendChild(this.iframe);
		return root;
	}

	this.onLoad = function() {
		var body = this.iframe.contentWindow.document.getElementsByTagName('body')[0];
		if (body) {
			body.style.backgroundColor = 'transparent';
			Event.removeListener(this.iframe, 'load', this.loadHandler);
			this.initDesignMode();
			this.fireLoaded();
		} else {
			window.setTimeout(this.onLoad, 250);
		}
	}
	
	this.onMouseEvent = function() {
		this.fireSelectionChange();
	}
	
	this.onKeyboardEvent = function(event) {
		if ('keyup' == event.type) {
			switch (event.keyCode) {
				case core.ui.Event.KEY_LEFT_ARROW:
				case core.ui.Event.KEY_RIGHT_ARROW:
				case core.ui.Event.KEY_UP_ARROW:
				case core.ui.Event.KEY_DOWN_ARROW:
				case core.ui.Event.KEY_HOME:
				case core.ui.Event.KEY_END:
				case core.ui.Event.KEY_PAGE_UP:
				case core.ui.Event.KEY_PAGE_DOWN:
					this.fireSelectionChange();
					break;
			}
		}
	}
}